#!/usr/bin/env python

'''nagios-api - a REST-like, JSON API for Nagios

This provides a simple REST interface to Nagios3. See the README for
more information about this software.

Originally from:

    https://github.com/xb95/nagios-api

Copyright (c) 2011-2012 by Bump Technologies, Inc and other authors and
contributors. See the LICENSE file for full licensing information.

'''


import datetime
import os
import re
import sys
import time
import types
import atexit
import urllib
from gzip import GzipFile
from StringIO import StringIO
from optparse import OptionParser
from diesel import Application, Loop, Service, sleep
from diesel.logmod import log, levels
from diesel.util.lock import synchronized
from diesel.protocols import http
from json import dumps
from nagios import Comment, Downtime, Nagios
from werkzeug.exceptions import BadRequest
from signal import signal, SIGTERM

CMDFILE = None
CMD_ENABLED = False
LOG_ENABLED = False
URL_REGEX = re.compile(r'^/(\w+)(?:/([\w\d\.\+\-\s]+)?)?$')
NAGIOS = None
NLOG = []
NLOGLINES = 0
ALLOW_ORIGIN = None
PID_FILE = "/var/run/nagios-api.pid"


def _send_json(req, success, content):
    '''Internal JSON sender.

    '''
    global ALLOW_ORIGIN
    out = dumps({ 'success': success, 'content': content }, ensure_ascii=False)
    headers = {
        'Content-Length': len(out),
        'Content-Type': 'application/json'
    }
    if ALLOW_ORIGIN is not None:
        headers['Access-Control-Allow-Origin'] = ALLOW_ORIGIN
    if 'gzip' in req.headers.get('Accept-Encoding', ''):
        headers['Content-Encoding'] = 'gzip'
        strout = StringIO()
        f = GzipFile(fileobj=strout, mode='w')
        f.write(out)
        f.close()
        out = strout.getvalue()
    return http.Response(out, 200, headers)


def json_error(req, msg):
    '''Return an error message to the caller.

    '''
    return _send_json(req, False, msg)


def json_response(req, msg):
    '''Return an error message to the caller.

    '''
    return _send_json(req, True, msg)


def http_problems(req, objid, reqobj):
    '''Get a host->service mapping for "All Problems". This is
    the method to use to get the data needed to display a simmilar
    view to the nagios "Problems" view.

    '''
    global NAGIOS
    if objid is not None:
        return json_error(req, 'Unexpected object ID.')

    hosts = {}
    for host, host_data in NAGIOS.for_json().items():
        for service,service_data in host_data['services'].items():
            if (int(service_data['current_state']) == 0):
                del host_data['services'][service]
        if host_data['services']:
            hosts[host] = host_data
    return json_response(req, hosts)


def http_state(req, objid, reqobj):
    '''Get a host->service mapping and return the basic state. This is
    the method to use to have status scripts or web interfaces that
    contain basic overview information.

    This should return everything you need to show the status of the
    world.

    '''
    global NAGIOS
    if objid is not None:
        return json_error(req, 'Unexpected object ID.')
    return json_response(req, NAGIOS.for_json())


def http_objects(req, objid, reqobj):
    '''Get a host->service mapping. Does not return any data about what
    these objects are. This is to be used to efficiently get a list of
    everything that exists in the world.

    '''
    global NAGIOS
    if objid is not None:
        return json_error(req, 'Unexpected object ID.')
    ret = {}
    for host in NAGIOS.hosts:
        ret[host] = []
        if host in NAGIOS.services:
            for svc in NAGIOS.services[host]:
                ret[host].append(svc)
    return json_response(req, ret)


def http_host(req, objid, reqobj):
    '''Get a view of a single host.
    Shows all of the variables for the host object.
    '''
    global NAGIOS
    if not objid:
        return json_error(req, 'No hostname provided.')
    elif objid not in NAGIOS.hosts.keys():
        return json_error(req, 'Unknown hostname.')
    ret = {}
    ret['comment'] = []
    ret['downtime'] = []
    host_keys = [ x for x in dir(NAGIOS.hosts[objid]) if not x.startswith('__') ]
    for host_key in host_keys:
        value = getattr(NAGIOS.hosts[objid], host_key)
        if type(value) == types.StringType:
            ret[host_key] = value
        # return comment and downtime data too, if either exists
        elif type(value) == dict:
            for k, v in value.iteritems():
                if isinstance( v, Comment ):
                    ret['comment'].append( v.for_json() )
                elif isinstance( v, Downtime ):
                    ret['downtime'].append( v.for_json() )
    if objid in NAGIOS.services:
        ret['services'] = []
        for svc in NAGIOS.services[objid]:
            ret['services'].append(svc)
    return json_response(req, ret)


def http_service(req, objid, reqobj):
    '''Get a view of a single service on one host.
    Shows all of the variables for the host object.
    '''
    global NAGIOS
    if not objid:
        return json_error(req, 'No hostname provided.')
    elif objid not in NAGIOS.services.keys():
        return json_error(req, 'Unknown hostname.')
    ret = {}
    for service in NAGIOS.services[objid]:
        ret[service] = {}
        service_keys = [ x for x in dir(NAGIOS.services[objid][service]) if not x.startswith('__') ]
        for service_key in service_keys:
            value = getattr(NAGIOS.services[objid][service], service_key)
            if type(value) == types.StringType:
                ret[service][service_key] = value
    return json_response(req, ret)


def http_log(req, objid, reqobj):
    '''Return the recent Nagios log entries. This is useful if you just
    want to see what has happened recently. See the subscribe method if
    you want to be notified when new log lines are added.

    '''
    global NLOG, LOG_ENABLED
    if objid is not None:
        return json_error(req, 'Unexpected object ID.')
    if not LOG_ENABLED:
        return json_error(req, 'Log file parsing is not enabled on nagios-api.')
    return json_response(req, NLOG)


def http_disable_notifications(req, objid, reqobj):
    '''Given a service or host, disable notifications for it.

    If you specify the host parameter and also set 'services_too' to
    true, we will act on the host and all of the services on it.

    '''
    global NAGIOS, CMD_ENABLED
    if not CMD_ENABLED:
        return json_error(req, 'External commands not enabled on nagios-api.')
    if objid is not None:
        return json_error(req, 'Unexpected object ID.')

    host = reqobj.get('host')
    service = reqobj.get('service')
    services_too = reqobj.get('services_too', False)

    obj = NAGIOS.host_or_service(host, service)
    if obj is None:
        return json_error(req, 'Host or service not found.')

    now = int(time.time())
    if obj.service is not None:
        if not send_nagios_command('DISABLE_SVC_NOTIFICATIONS', host, service):
            return json_error(req, 'Failed sending command to Nagios.')
    else:
        if not send_nagios_command('DISABLE_HOST_NOTIFICATIONS', host):
            return json_error(req, 'Failed sending command to Nagios.')
        if services_too:
            if not send_nagios_command('DISABLE_HOST_SVC_NOTIFICATIONS', host):
                return json_error(req, 'Failed sending command to Nagios.')
    return json_response(req, 'disabled')


def http_enable_notifications(req, objid, reqobj):
    '''Given a service or host, enable notifications for it.

    If you specify the host parameter and also set 'services_too' to
    true, we will act on the host and all of the services on it.

    '''
    global NAGIOS, CMD_ENABLED
    if not CMD_ENABLED:
        return json_error(req, 'External commands not enabled on nagios-api.')
    if objid is not None:
        return json_error(req, 'Unexpected object ID.')

    host = reqobj.get('host')
    service = reqobj.get('service')
    services_too = reqobj.get('services_too', False)

    obj = NAGIOS.host_or_service(host, service)
    if obj is None:
        return json_error(req, 'Host or service not found.')

    now = int(time.time())
    if obj.service is not None:
        if not send_nagios_command('ENABLE_SVC_NOTIFICATIONS', host, service):
            return json_error(req, 'Failed sending command to Nagios.')
    else:
        if not send_nagios_command('ENABLE_HOST_NOTIFICATIONS', host):
            return json_error(req, 'Failed sending command to Nagios.')
        if services_too:
            if not send_nagios_command('ENABLE_HOST_SVC_NOTIFICATIONS', host):
                return json_error(req, 'Failed sending command to Nagios.')
    return json_response(req, 'enabled')


def http_schedule_downtime(req, objid, reqobj):
    '''Given a service or host, schedule downtime for it. The main mode
    for this API is to schedule a hard downtime that starts now and ends
    after so many seconds.

    If you specify the host parameter and also set 'services_too' to
    true, we will schedule downtime for the host and the services on it.

    '''
    global NAGIOS, CMD_ENABLED
    if not CMD_ENABLED:
        return json_error(req, 'External commands not enabled on nagios-api.')
    if objid is not None:
        return json_error(req, 'Unexpected object ID.')

    now = int(time.time())

    host = reqobj.get('host')
    service = reqobj.get('service')
    services_too = reqobj.get('services_too', False)
    author = reqobj.get('author', 'nagios-api')
    comment = reqobj.get('comment', 'schedule downtime')
    start_time = reqobj.get('start_time', now)
    fixed = reqobj.get('fixed', 1)
    try:
        dur = int(reqobj.get('duration', 0))
    except ValueError:
        dur = 0
    end_time = reqobj.get('end_time', start_time + dur)

    obj = NAGIOS.host_or_service(host, service)
    if obj is None:
        return json_error(req, 'Host or service not found.')
    if dur < 60 or dur > 86400 * 7:  # Upper limit?
        return json_error(req, 'Downtime must be between 60 seconds and a week.')

    if obj.service is not None:
        if not send_nagios_command('SCHEDULE_SVC_DOWNTIME', host, service, start_time,
                                   end_time, fixed, 0, dur, author, comment):
            return json_error(req, 'Failed sending command to Nagios.')
    else:
        if not send_nagios_command('SCHEDULE_HOST_DOWNTIME', host, start_time, end_time,
                                   fixed, 0, dur, author, comment):
            return json_error(req, 'Failed sending command to Nagios.')
        if services_too:
            if not send_nagios_command('SCHEDULE_HOST_SVC_DOWNTIME', host, start_time, end_time,
                                       fixed, 0, dur, author, comment):
                return json_error(req, 'Failed sending command to Nagios.')
    return json_response(req, 'scheduled')


def http_cancel_downtime(req, objid, reqobj):
    '''Given a downtime_id in objid, cancel that downtime. Alternately,
    this method will expect a host and/or service parameter and use that
    to cancel any matching downtimes.

    '''
    global NAGIOS, CMD_ENABLED
    if not CMD_ENABLED:
        return json_error(req, 'External commands not enabled on nagios-api.')

    def cancel_downtime(obj):
        if obj.service is not None:
            return send_nagios_command('DEL_SVC_DOWNTIME', dt.downtime_id)
        else:
            return send_nagios_command('DEL_HOST_DOWNTIME', dt.downtime_id)

    dts = []
    if objid is not None:
        if objid not in NAGIOS.downtimes:
            return json_error(req, 'Downtime ID does not seem valid.')
        dts += [NAGIOS.downtimes[objid]]
    else:
        host = reqobj.get('host')
        service = reqobj.get('service')
        services_too = reqobj.get('services_too', False)
        obj = NAGIOS.host_or_service(host, service)
        if obj is None:
            return json_error(req, 'Failed to get host or service for downtime.')
        if obj.service is None and services_too:
            for svc in obj.services.itervalues():
                dts += svc.downtimes.values()
        dts += obj.downtimes.values()

    res = None
    for dt in dts:
        if res is None:
            res = True  # So we know if we found any.
        res = cancel_downtime(dt) and res

    if res is not None:
        if res:
            return json_response(req, 'cancelled')
        else:
            return json_error(req, 'One or more cancels failed.  Some may have succeeded.')
    else:
        return json_response(req, 'none found')



def http_schedule_hostgroup_downtime(req, objid, reqobj):
    '''Given a hostgroup, schedule downtime for all the hosts in the hostgroup.
    The main mode for this API is to schedule a hard downtime that starts now and ends
    after so many seconds.

    If you specify the hostgroup parameter and also set 'services_too' to
    true, we will schedule downtime for the all the hosts and the services on it.

    '''
    global NAGIOS, CMD_ENABLED
    if not CMD_ENABLED:
        return json_error(req, 'External commands not enabled on nagios-api.')
    if objid is not None:
        return json_error(req, 'Unexpected object ID.')

    now = int(time.time())

    hostgroup = reqobj.get('hostgroup')
    services_too = reqobj.get('services_too', False)
    author = reqobj.get('author', 'nagios-api')
    comment = reqobj.get('comment', 'schedule downtime')
    start_time = reqobj.get('start_time', now)
    fixed = reqobj.get('fixed', 1)
    try:
        dur = int(reqobj.get('duration', 0))
    except ValueError:
        dur = 0
    end_time = reqobj.get('end_time', start_time + dur)

    if hostgroup is None:
        return json_error(req, 'Hostgroup not specified.')
    if dur < 60 or dur > 86400 * 7:  # Upper limit?
        return json_error(req, 'Downtime must be between 60 seconds and a week.')

    if not send_nagios_command('SCHEDULE_HOSTGROUP_HOST_DOWNTIME', hostgroup, start_time,
                                end_time, fixed, 0, dur, author, comment):
        return json_error(req, 'Failed sending command to Nagios.')
    if services_too:
        if not send_nagios_command('SCHEDULE_HOSTGROUP_SVC_DOWNTIME', hostgroup, start_time, end_time,
                                    fixed, 0, dur, author, comment):
            return json_error(req, 'Failed sending command to Nagios.')
    return json_response(req, 'scheduled')



def http_submit_result(req, objid, reqobj):
    '''Submits a check result for a host or service. This is mostly used
    for passive services where Nagios is not responsible for doing the
    checks itself.

    As with other APIs, you are required to supply a host parameter and
    you may supply a service. The output parameter is an opaque string
    that gets given to Nagios. Use the status parameter to specify an
    integer return code.

    Host status codes: 0 = UP, 1 = DOWN, 2 = UNREACHABLE.
    Service status codes: 0 = OK, 1 = WARNING, 2 = CRITICAL, 3 = UNKNOWN.

    '''
    global NAGIOS, CMD_ENABLED
    if not CMD_ENABLED:
        return json_error(req, 'External commands not enabled on nagios-api.')

    host = reqobj.get('host')
    service = reqobj.get('service')

    obj = NAGIOS.host_or_service(host, service)
    if obj is None:
        return json_error(req, 'Failed to find host or service to update.')

    if 'status' not in reqobj:
        return json_error(req, 'Required parameter "status" not found.')
    if 'output' not in reqobj:
        return json_error(req, 'Required parameter "output" not found.')

    try:
        status = int(reqobj['status'])
    except ValueError:
        return json_error(req, 'Invalid status provided.')

    if obj.service is not None:
        if not send_nagios_command('PROCESS_SERVICE_CHECK_RESULT', host,
                                   service, status, reqobj['output']):
            return json_error(req, 'Failed sending command to Nagios.')
    else:
        if not send_nagios_command('PROCESS_HOST_CHECK_RESULT', host, status,
                                   reqobj['output']):
            return json_error(req, 'Failed sending command to Nagios.')
    return json_response(req, 'submitted')


def http_acknowledge_problem(req, objid, reqobj):
    '''Submits an acknowledgement of a host or service problem. This is used
    when you have an open issue, that you want to acknowledge to prevent it
    from sending further alerts.

    You are required to send at minimum a host parameter, and you can also
    supply an optional service parameter. If no service is supplied, the
    host will be acknowledged, and if the service parameter is supplied
    only the specific service will be acknowledged.

    Additionally, you are required to supply a comment parameter as well. This
    is a text string that will be used to identify the reason for the
    acknowledgement.

    The following options are also available, but optional.

    sticky (0/1): If set to 1, the acknowledgement will remain until the service
    or host enters an OK state. If set to 0, it will clear the acknowledgement on
    the first state change, ex. CRITICAL->WARNING. Defaults to 1 (on).

    notify (0/1): If set to 0, no notification will be sent to the configure
    contacts for the host or service. Defaults to 1 (on).

    persistent (0/1): If set to 1, the comment will remain even after the service
    or host problem has been resolved. Defaults to 0 (off).

    author (string): This is the name displayed as the creator of the
    acknowledgement. This will default to 'nagios-api'.

    '''
    global NAGIOS, CMD_ENABLED
    if not CMD_ENABLED:
        return json_error(req, 'External commands not enabled on nagios-api.')

    host = reqobj.get('host')
    service = reqobj.get('service')
    comment = reqobj.get('comment')
    author = reqobj.get('author', 'nagios-api')
    sticky = reqobj.get('sticky', 1)
    notify = reqobj.get('notify', 1)
    persistent = reqobj.get('persistent', 0)

    obj = NAGIOS.host_or_service(host, service)
    if obj is None:
        return json_error(req, 'Failed to find host or service to update.')

    if not 'comment':
        return json_error(req, 'Required parameter "comment" not found.')

    try:
        sticky = int(sticky)
        notify = int(notify)
        persistent = int(persistent)
    except ValueError:
        return json_error(req, 'Invalid value provided for one or more of sticky, notify or persistent')

    if obj.service is not None:
        if not send_nagios_command('ACKNOWLEDGE_SVC_PROBLEM', host, service,
                                    sticky, notify, persistent, author, comment):
            return json_error(req, 'Failed sending command to Nagios.')
    else:
        if not send_nagios_command('ACKNOWLEDGE_HOST_PROBLEM', host, sticky,
                                    notify, persistent, author, comment):
            return json_error(req, 'Failed sending command to Nagios.')
    return json_response(req, 'submitted')


def http_remove_acknowledgement(req, objid, reqobj):
    '''Removes an acknowledgement from a host or service.

    You are required to send at minimum a host parameter, and
    you can also supply an optional service parameter. If no
    service is supplied, the host will be acknowledged, and if the
    service parameter is supplied only the specific service will be
    acknowledged.

    '''
    global NAGIOS, CMD_ENABLED
    if not CMD_ENABLED:
        return json_error(req, 'External commands not enabled on nagios-api.')

    host = reqobj.get('host')
    service = reqobj.get('service')

    obj = NAGIOS.host_or_service(host, service)
    if obj is None:
        return json_error(req, 'Failed to find host or service to update.')

    if obj.service is not None:
        if not send_nagios_command('REMOVE_SVC_ACKNOWLEDGEMENT', host, service):
            return json_error(req, 'Failed sending command to Nagios.')
    else:
        if not send_nagios_command('REMOVE_HOST_ACKNOWLEDGEMENT', host):
            return json_error(req, 'Failed sending command to Nagios.')
    return json_response(req, 'submitted')


def http_add_comment(req, objid, reqobj):
    '''Adds a comment to a host or service.

    You are required to send at minimum a host and comment parameter,
    and you can also supply an optional service parameter. If the
    service parameter is supplied, the comment will be added to the
    specific service and if its omitted, the comment will be added to
    the host

    The following options are also available, but optional.

    persistent (0/1): If set to 1, the comment will remain until
    manually deleted, if set to 0, it will automatically be purged at
    the next restart of the Nagios process. Defaults to 0 (off)

    author (string): This is the name displayed as the creator of the
    comment. This will default to 'nagios-api'.

    '''
    global NAGIOS, CMD_ENABLED
    if not CMD_ENABLED:
        return json_error(req, 'External commands not enabled on nagios-api.')

    host = reqobj.get('host')
    service = reqobj.get('service')
    persistent = reqobj.get('persistent', 0)
    comment = reqobj.get('comment')
    author = reqobj.get('author', 'nagios-api')

    obj = NAGIOS.host_or_service(host, service)
    if obj is None:
        return json_error(req, 'Failed to find host or service to update.')

    if not comment:
        return json_error(req, 'Required parameter "comment" not found.')

    try:
        persistent = int(persistent)
    except ValueError:
        return json_error(req, 'Invalid value provided for persistent')

    if obj.service is not None:
        if not send_nagios_command('ADD_SVC_COMMENT', host, service,
                                    persistent, author, comment):
            return json_error(req, 'Failed sending command to Nagios.')
    else:
        if not send_nagios_command('ADD_HOST_COMMENT', host,
                                    persistent, author, comment):
            return json_error(req, 'Failed sending command to Nagios.')
    return json_response(req, 'submitted')


def http_delete_comment(req, objid, reqobj):
    '''Deletes one or all comments for a host or service.

    You are required to supply atleast the host parameter along with
    comment_id, detailing the comment to delete. You can also supply the
    service parameter to delete comments from a specific service.

    If comment_id is set to -1, all comments for the host or service
    will be deleted.

    '''
    global NAGIOS, CMD_ENABLED
    if not CMD_ENABLED:
        return json_error(req, 'External commands not enabled on nagios-api.')

    host = reqobj.get('host')
    service = reqobj.get('service')
    comment_id = reqobj.get('comment_id')

    obj = NAGIOS.host_or_service(host, service)
    if obj is None:
        return json_error(req, 'Failed to find host or service to update.')

    if not 'comment_id':
        return json_error(req, 'Required parameter "comment" not found.')

    try:
        comment_id = int(comment_id)
    except ValueError:
        return json_error(req, 'Invalid value provided for comment_id')

    if obj.service is not None:
        if comment_id == -1:
            if not send_nagios_command('DEL_ALL_SVC_COMMENTS', host, service):
                return json_error(req, 'Failed sending command to Nagios.')
        else:
            if not send_nagios_command('DEL_SVC_COMMENT', comment_id):
                return json_error(req, 'Failed sending command to Nagios.')
    else:
        if comment_id == -1:
            if not send_nagios_command('DEL_ALL_HOST_COMMENTS', host):
                return json_error(req, 'Failed sending command to Nagios.')
        else:
            if not send_nagios_command('DEL_HOST_COMMENT', comment_id):
                return json_error(req, 'Failed sending command to Nagios.')
    return json_response(req, 'submitted')


def http_schedule_check(req, objid, reqobj):
    '''Schedules a check for a host or service.

    Requires the host parameter to be sent, and also accepts a service
    parameter. If you wish to schedule checks for all services on a host,
    you can use the all_services parameter.

    Scheduling a non-forced check, does not mean the check will occur.
    If active checks have been disabled either on a program-wide or
    service level, or the services are already scheduled to be checked
    sooner than your requested time, the check will not be performed,
    unless forced mode is enabled (see below).

    The following optional parameters are also available:

    check_time (int): The time at which to schedule the check, in time_t
    (UNIX timestamp) format. Defaults to current time.

    forced (0/1): If set to 1, the scheduled check will be marked as
    forced, meaning it will be performed, even if there has been other
    checks run on the host or service in the mean time. Defaults to 0
    (off).

    all_services (0/1): If set to 1, all services for the specified
    host will be checked. Enabling this option, will cause the service
    parameter to be ignored, if supplied. Defaults to 0 (off).

    '''
    from time import time
    global NAGIOS, CMD_ENABLED
    if not CMD_ENABLED:
        return json_error(req, 'External commands not enabled on nagios-api.')

    host = reqobj.get('host')
    service = reqobj.get('service')
    check_time = reqobj.get('check_time', time())
    forced = reqobj.get('forced', 0)
    all_services = reqobj.get('all_services', 0)

    obj = NAGIOS.host_or_service(host, service)
    if obj is None:
        return json_error(req, 'Failed to find host or service to update.')

    try:
        check_time = int(check_time)
        forced = int(forced)
        all_services = int(all_services)
    except ValueError:
        return json_error(req, 'Invalid value provided for check_time, all_services or forced')

    if obj.service is not None:
        if forced:
            if not send_nagios_command('SCHEDULE_FORCED_SVC_CHECK', host, service, check_time):
                return json_error(req, 'Failed sending command to Nagios.')
        else:
            if not send_nagios_command('SCHEDULE_SVC_CHECK', host, service, check_time):
                return json_error(req, 'Failed sending command to Nagios.')
    else:
        if forced:
            if not send_nagios_command('SCHEDULE_FORCED_HOST_CHECK', host, check_time):
                return json_error(req, 'Failed sending command to Nagios.')
        else:
            if not send_nagios_command('SCHEDULE_HOST_CHECK', host, check_time):
                return json_error(req, 'Failed sending command to Nagios.')

        if all_services:
            if forced:
                if not send_nagios_command('SCHEDULE_FORCED_HOST_SVC_CHECKS', host, check_time):
                    return json_error(req, 'Failed sending command to Nagios.')
            else:
                if not send_nagios_command('SCHEDULE_HOST_SVC_CHECKS', host, check_time):
                    return json_error(req, 'Failed sending command to Nagios.')

    return json_response(req, 'submitted')


def http_handler(req):
    '''Handle an incoming HTTP request.

    '''
    # All requests should follow this very simple format.
    global URL_REGEX
    url = req.script_root + req.path
    url = urllib.unquote(url).decode('utf8')
    res = URL_REGEX.match(url)
    if res is None:
        return json_error(req, 'Invalid request URI')
    verb, objid = res.group(1), res.group(2)
    try:
        objid = int(objid)
    except ValueError:
        pass
    except TypeError:
        objid = None

    # If it's a POST, try to extract a JSON object from the body.
    reqobj = None
    if req.method == 'POST':
        try:
            reqobj = req.json
        except BadRequest:
            return json_error(req, 'Invalid JSON body')

        if reqobj is None:
            return json_error(req, 'No JSON detected, did you set '
                    'the Content-Type to application/json?')

    # Dispatch table goes here
    dispatch = {
        'GET': {
            'log': http_log,
            'state': http_state,
            'objects': http_objects,
            'host': http_host,
            'service': http_service,
            'problems': http_problems,
        },
        'POST': {
            'cancel_downtime': http_cancel_downtime,
            'schedule_downtime': http_schedule_downtime,
            'schedule_hostgroup_downtime': http_schedule_hostgroup_downtime,
            'disable_notifications': http_disable_notifications,
            'enable_notifications': http_enable_notifications,
            'submit_result': http_submit_result,
            'acknowledge_problem': http_acknowledge_problem,
            'remove_acknowledgement': http_remove_acknowledgement,
            'add_comment': http_add_comment,
            'delete_comment': http_delete_comment,
            'schedule_check': http_schedule_check,
        }
    }

    if req.method not in dispatch:
        return json_error(req, 'Method %s not supported' % req.method)
    if verb not in dispatch[req.method]:
        return json_error(req, 'Verb %s (method %s) not supported' % (verb, req.method))
    return dispatch[req.method][verb](req, objid, reqobj)


def send_nagios_command(*args):
    '''Send a simple command to our local Nagios server.

    '''
    global CMDFILE, CMD_ENABLED
    if not CMD_ENABLED:
        return False  # May not be enabled.
    if len(args) < 2:
        return False
    arg = '[%d] ' % int(time.time()) + ';'.join(unicode(j) for j in args)
    arg = arg.encode('utf-8')
    with synchronized():
        log.info('Sending command: %s' % arg)
        try:
          fd = os.open(CMDFILE, os.O_WRONLY)
          os.write(fd, arg + '\n')
          os.close(fd)
        except Exception as e:
          log.error(e)
          return False
        return True
    return True


def read_status(statusfile):
    '''Monitor the Nagios status file and update our global data store
    with the data as it changes.

    '''
    global NAGIOS
    mtime = None
    while True:
        stat = os.stat(statusfile)
        if mtime is None or stat.st_mtime > mtime:
            try:
               NAGIOS = Nagios(statusfile)
            except ValueError,e:
                print "You appear to have handed me a malformed status file - possibly the state retention file. Please check your arguments and try again."
                sys.exit(-1)
            mtime = stat.st_mtime
        sleep(1)


def read_log(logfile):
    '''This function reads the Nagios log file and parses events. This
    allows us to provide a pubsub style interface so people can get
    real-time updates from the Nagios system.

    '''
    global NLOG, NLOGLINES
    f = open(logfile, 'r')
    cts = 0
    while True:
        loc = f.tell()
        line = f.readline()
        if not line:
            sleep(1)
            f.seek(loc)
            cts += 1 # Handle log rollovers by watching for no activity.
            if cts >= 60:
                f = open(logfile, 'r')
                cts = 0
        else:
            NLOGLINES += 1
            NLOG.append(line.strip())
            NLOG = NLOG[-1000:]  # Keep the most recent 1k lines.
    f.close()  # Useless?

def write_pid():
    '''Write pid file

    '''
    global PID_FILE
    if os.path.isfile(PID_FILE):
        sys.stderr.write("%s already exists, exiting\n" % PID_FILE)
        sys.exit(1)
    else:
        try:
            file(PID_FILE, 'w').write(str(os.getpid()))
        except Exception as e:
            sys.stderr.write("%s\n" % e)
            sys.stderr.write("Unable to write pid file: %s\n" % PID_FILE)
            sys.exit(1)

def main(argv):
    '''A simple REST API for Nagios3.

    '''
    global CMDFILE, CMD_ENABLED, LOG_ENABLED, ALLOW_ORIGIN, PID_FILE
    app = Application()

    parser = OptionParser(description='Give Nagios a REST API.')
    parser.add_option('-o', '--allow-origin', dest='alloworigin', metavar='ORIGIN',
            help='Access-Control-Allow-Origin header contents')
    parser.add_option('-s', '--status-file', dest='statusfile', metavar='FILE',
            default='/var/cache/nagios3/status.dat', help='The file that contains '
            'the Nagios status.')
    parser.add_option('-c', '--command-file', dest='commandfile', metavar='FILE',
            default='/var/lib/nagios3/rw/nagios.cmd', help='The file to write '
            'Nagios commands to.')
    parser.add_option('-l', '--log-file', dest='logfile', metavar='FILE',
            default='/var/log/nagios3/nagios.log', help='The file Nagios writes '
            'log events to.')
    parser.add_option('-b', '--bind', dest='bind_addr', metavar='ADDR',
            default='', help='The address to listen for requests on.')
    parser.add_option('-p', '--port', dest='port', metavar='PORT', type='int',
            default=6315, help='The port to listen for requests on.')
    parser.add_option('-f', '--pid-file', dest='pid_file', metavar='PID_FILE',
            default=PID_FILE, help='File to write pid to')
    parser.add_option('-q', '--quiet', dest='quiet', action='store_true',
            help='Quiet mode')
    (options, args) = parser.parse_args(args=argv[1:])

    if not os.path.isfile(options.statusfile):
        parser.error('Status file not found: %s' % options.statusfile)
    if options.port < 0 or options.port > 65535:
        parser.error('Port must be in the range 1..65535.')

    if options.quiet:
        log.min_level = levels.WARNING

    if os.path.exists(options.commandfile):
        CMD_ENABLED = True
        CMDFILE = options.commandfile
    if options.alloworigin:
        ALLOW_ORIGIN = options.alloworigin

    PID_FILE = options.pid_file
    write_pid()

    log.info('Listening on port %d, starting to rock and roll!' % options.port)
    app.add_service(Service(http.HttpServer(http_handler), options.port,
            options.bind_addr))
    app.add_loop(Loop(read_status, options.statusfile), keep_alive=True)
    if os.path.isfile(options.logfile):
        LOG_ENABLED = True
        app.add_loop(Loop(read_log, options.logfile), keep_alive=True)
    app.run()
    return 1


def _exitfunc(*args):
    global PID_FILE
    if PID_FILE is not None and os.path.isfile(PID_FILE):
        print "Cleaning up PID."
        os.unlink(PID_FILE)
    print "Exiting."
    sys.exit(0)


if __name__ == '__main__':
    pid = str(os.getpid())

    atexit.register(_exitfunc)
    signal(SIGTERM, _exitfunc)

    sys.exit(main(sys.argv))
